5.01. Асинхронность в JavaScript
Асинхронность в JavaScript
JavaScript использует механизм асинхронности, обеспечивая одновременное выполнение нескольких задач «параллельно» без ожидания окончания предыдущих в очереди. Важно подчеркнуть, что JS однопоточный язык, а параллелизм имитируется благодаря event loop и асинхронным API.
Думаю, что этот абзац может быть очень непонятным для неподготовленного новичка. И это нормально. Давайте по полочкам.
JavaScript - однопоточный язык программирования. Это значит, что в каждый момент времени он может выполнять только одну инструкцию. Представьте, что у вас есть одна линия конвейера, по которой проходит одна деталь за раз. Никакого параллельного выполнения, как в многопоточных языках (например, Java или C#), в чистом JS нет. Инструкция 1, потом Инструкция 2. И пока Инструкция 1 не выполнится, поток блокируется и мы не переходим к Инструкции 2.
Но если JS выполняет только одну задачу за раз — как тогда он может загружать данные с сервера, реагировать на клики, устанавливать таймеры, читать файлы — и при этом не подвисать? Ответ - за счёт асинхронной модели выполнения, построенной на Event Loop и асинхронных API браузера или среды выполнения (например, Node.js).
Таким образом, здесь асинхронность не является многопоточностью.
Когда вы пишете:
console.log("1");
setTimeout(() => console.log("2"), 0);
console.log("3");
То можете ожидать из природы интерпретируемости JS, что выйдет результат 1, 2, 3, так как задержка в setTimeout указана 0 миллисекунд. Но вывод будет иным - 1, 3, 2. Почему? Потому что setTimeout — асинхронная операция, и она не блокирует выполнение кода. Вместо этого она передаёт свою функцию-коллбэк в внешнюю систему (браузер или Node.js), которая запускает таймер в фоне, а сам JS продолжает работать дальше. Именно Event Loop отвечает за то, когда и в каком порядке эти отложенные функции будут вызваны — после завершения текущей синхронной работы.
Чтобы понять асинхронность, нужно разобраться с архитектурой выполнения кода в JS. Она состоит из нескольких ключевых компонентов:
- Стек вызовов (Call Stack) - это стек, в котором хранятся функции, которые сейчас выполняются. JS работает по принципу LIFO (Last In, First Out) — последняя добавленная функция выполняется первой.
function greet() {
console.log("Привет!");
}
greet(); // → добавляется в стек, выполняется, удаляется
Когда greet() вызывается, она попадает в стек. После завершения — удаляется.
- Web APIs (или Host APIs) - это внешние среды, предоставляемые браузером или Node.js, такие как:
- setTimeout, setInterval
- fetch, XMLHttpRequest
- addEventListener
- requestAnimationFrame
- Работа с DOM, файлами и т.д.
Когда вы вызываете setTimeout, JS не выполняет таймер сам. Он передаёт задачу в Web API, которое начинает отсчёт времени в фоне, независимо от JS. Поэтому и так получается - 1, 3, 2.
- Очереди: Task Queue (Macrotasks) и Microtask Queue. То есть, бывает два вида очередей - микрозадачи и макрозадачи. Когда асинхронная операция завершается, её коллбэк не выполняется сразу. Он помещается в одну из двух очередей:
Микрозадачи (очередь микрозадач) - высокий приоритет, и сюда попадают .then, .catch, .finally от промисов (Promise), queueMicrotask(), MutationObserver (отслеживание изменений DOM). Все микрозадачи выполняются до следующей макрозадачи.
Макрозадачи (очередь макрозадач) - низкий приоритет, и сюда попадают setTimeout, setInterval, setImmediate (в Node.js), события (click, input и т.д.), I/O операции. Одна задача из этой очереди выполняется за один цикл Event Loop.
- Event Loop (Цикл событий) - механизм, который постоянно проверяет:
- пуст ли стек вызовов?
- есть ли микрозадачи? Если да, то выполняет все из очереди микрозадач;
- есть ли макрозадачи? Если да, то выполняет одну из очереди макрозадач;
- возвращается к шагу (проверка стека вызовов).
Такой вот цикл - это работает по кругу снова и снова. Event Loop — это не поток и не таймер. Это просто цикл, который проверяет стек и очереди.
Пример.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
Код выглядит простым, и давайте глянем, как всё работает:
console.log("1")— синхронный вызов → выполняется сразу → выводит 1;setTimeout(...)— передаёт коллбэк в Web API, которое начинает таймер (0 мс). Коллбэк ещё не в очереди, но будет помещён в очередь макрозадач, когда таймер сработает;Promise.resolve().then(...)— промис уже выполнен, .then помещает коллбэк в очередь микрозадач;console.log("4")— синхронный вызов → выполняется → выводит 4;- Стек вызовов пуст → Event Loop начинает работать;
- Проверка очереди микрозадач → есть одна задача
(console.log("3"))→ выполняется → выводит 3; - Очередь микрозадач пуста → Event Loop переходит к очереди макрозадач;
- Очередь макрозадач → есть setTimeout → выполняется → выводит 2.
Итого, в результате мы увидим 1, 4, 3, 2.
Важно: даже если setTimeout установлен на 0, он всегда ждёт, пока все микрозадачи будут выполнены. Промисы всегда приоритетнее таймеров.
Почему промисы в микрозадачах, а setTimeout — в макрозадачах? Потому что промисы — это часть логики программы, и их обработка должна быть немедленной и предсказуемой. Если вы используете .then(), вы ожидаете, что он выполнится как только промис разрешится, без задержек. В то время как setTimeout — это планировщик, и его задача — отложить выполнение на следующий "кадр" или цикл.
Пример:
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
Что происходит:
- fetch — асинхронная операция → передаётся в Web API (браузер).
- JS не ждёт ответа → продолжает выполнять следующие строки.
- Когда сервер ответит, Web API помещает коллбэк из .then в очередь микрозадач.
- Как только стек освободится, Event Loop выполнит этот коллбэк.
- Таким образом, основной поток не блокируется, и страница остаётся отзывчивой.
А что такое асинхронная функция? Как можно понять, это будет как-то так:
async function load() {
console.log("A");
await fetch("/api"); // → ожидание
console.log("B");
}
Здесь создаётся асинхронная функция load(), которая логирует A, потом выполняет fetch по адресу /api, ожидает и логирует B.
Функция становится асинхронной не потому, что она использует await, а потому что:
- она помечена как async - всегда возвращает промис;
- внутри неё можно использовать await - логика приостанавливается на промисе;
- после await происходит продолжение функции - это микрозадача.
async function f() {
await 1; // await с примитивом → промис, разрешённый сразу
console.log("after await");
}
f();
console.log("sync");
Здесь создаётся асинхронная функция f(), которая ожидает 1 - это промис, который ожидает примитив, потом логирует «after await». А глобально происходит вызов f(), затем логирование sync. Вывод, как можно понять, будет сначала sync, потом after await. Потому что await это обёрнутый промис .then(), который является микрозадачей. А микрозадачи всегда выполняются после синхронного кода.
Итого, сначала синхронный код, потом все микрозадачи, потом одна макрозадача. Коллбэки сразу не вызываются, а попадают в очереди и ждут. А acync/await называют синтаксическим сахаром, потому что работа выполняется на том же механизме - промисы являются микрозадачами.
И если бы вам нужно было вывести и выполнить всё по порядку без какого-то ожидания…вы бы просто делали синхронный код. Да, у вас будет 1, 2, 3, к примеру, но если вывод «2» требует запроса к серверу, который может грузиться или считать какое-то время…то у вас программа остановится на этом месте и будет ждать, пока сервер соизволит ответить. И JavaScript всё ещё остаётся однопоточным, и делает вид, что выполняет несколько задач параллельно, благодаря Event Loop.
Механизмы асинхронности:
- Callback – функции, вызываемые после завершения операции.
- Promises (Промисы) – более удобная альтернатива колбэкам.
- Async/Await – синтаксический сахар над промисами.
Разберём их более детально.
★ Callback – фундаментальный паттерн, когда функция передаётся как аргумент и выполняется после завершения асинхронной операции. То есть, callback-функции – это функции, которые передаются в другую функцию и выполняются после какого-то события.
Представим, что мы просим друга сделать что-то и говорим ему: «Вот инструкция (функция). Выполни её, когда закончишь своё дело». Мы – JS-код, а друг – асинхронная функция:
| сходи в магазин | асинхронная операция |
|---|---|
| а когда вернёшься – позвони мне | callback |
Коллбек - это функция, переданная как аргумент в другую функцию, чтобы вызвать её позже, когда какая-то асинхронная операция завершится. Коллбэк — не ключевое слово. Это просто параметр, который ожидает функцию.
Давайте разберём шаблон создания функции с коллбеком:
function имяФункции(параметры, callback) {
// какая-то асинхронная операция
// например: setTimeout, fetch, чтение файла и т.д.
// когда операция завершится:
if (успех) {
callback(null, результат); // первый аргумент — ошибка (null если успех)
} else {
callback(ошибка, null); // ошибка в первом аргументе
}
}
Стоит сразу отметить, что в Node.js и многих библиотеках используется ошибка первым аргументом (error-first callback). Это стандарт. Но давайте разбирать вышеприведенный шаблон.
Здесь, как мы видим, происходит обычное объявление обычной функции через function имяФункции(). К примеру, это может быть function fetchData(url, callback).
Далее мы видим, что в параметрах функции есть callback. Это один из параметров, в который передадут функцию. То есть, callback это не ключевое слово, а просто имя параметра, можно cb, onDone, handler, да что угодно.
И мы видим, что в тексте есть два вызова callback:
callback(null, data)- вызов коллбека с данными, к примеру при успехе будет callback(null, "данные");callback(error, null)- вызов при ошибке, к примеру,callback("Ошибка сети", null).
Сложно? Технически, получается, вызывая эту функцию, мы просто в том месте, где callback, передаем функцию. К примеру, имяФункции(аргумент, имяФункцииКоллбека).
Давайте ещё пример:
function loadData(callback) {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
callback(null, "Данные загружены!");
} else {
callback("Ошибка загрузки", null);
}
}, 1000);
}
Здесь абсолютный аналог. callback - просто имя параметра, которое можете назвать как угодно, но мы будем подразумевать, что будем вызывать его. А в loadData(...) мы передаём функцию как аргумент — это и есть коллбэк.
Что следует запомнить:
- Коллбэки не возвращают результат — они вызываются, когда операция завершится.
- Коллбэки не имеют встроенной обработки ошибок — нужно вручную проверять первый аргумент.
Погнали дальше.
function loadData(callback) { // функция принимает callback
setTimeout(() => { //происходит эмуляция асинхронности
callback("Данные загружены!"); //через 1 секунду вызываем callback с результатом
}, 1000);
}
loadData((result) => { // передаём callback-функцию
console.log(result); // она сработает через 1 сек: "Данные загружены!"
});
В вышеприведённом примере это работает так, пошагово:
- loadData – это функция, которая ждёт callback (друг, ждущий инструкцию);
- Внутри loadData есть setTimeout – он имитирует долгую операцию (например, запрос к серверу).
- Через 1 секунду setTimeout вызывает callback("Данные загружены!").
- Мы передаём анонимную функцию (result)
=>{ console.log(result); }как callback. - Когда setTimeout срабатывает – callback выполняется, и в консоль выводится результат.
Callback применяется для загрузок данных с сервера, чтения файлов, таймеров (setTimeout, setInterval), обработки событий, или работы с API.
Пример (setTimeout):
setTimeout(() => {
console.log("Прошло 2 секунды!");
}, 2000);
() => { console.log(…) } – это callback.
Он выполнится после того, как пройдёт 2 секунды.
Пример (AJAX):
function fetchData(url, callback) {
fetch(url)
.then(response => response.json())
.then(data => callback(data)) // Передаём данные в callback
.catch(error => console.error(error));
}
fetchData("https://api.example.com/data", (data) => {
console.log("Получены данные:", data);
});
(data) => { console.log(data); } – callback, который сработает после загрузки данных.
Пример (обработчик клика):
button.addEventListener("click", () => {
console.log("Кнопка нажата!");
});
Вся стрелочная функция – это callback.
Она выполнится после клика на кнопку.
Таким образом, callback – это функция, которая выполнится после какого-то события.
★ Promises (промисы) – объекты-обёртки для асинхронных операций, которые:
- ждут завершения (pending);
- по завершении получают fulfilled или rejected (возвращают результат или ошибку);
- позволяют цеплять обработчики (then/catch).
Pending → Fullfulled с результатом или Rejected с ошибкой.
Promise (от английского) – «обещание» JavaScript сделать что-то асинхронное и сообщить результат: успех (fullfulled), ошибка (rejected), ожидание (pending) – ещё выполняется.

Итого, промисом является некий объект, представляющий будущий результат асинхронной операции, который может быть в одном из трёх состояний - ожидание, успех или ошибка. Давайте подготовим шаблон для создания промиса и разберём его:
const имяПеременной = new Promise((resolve, reject) => {
// асинхронная операция (таймер, запрос, чтение файла...)
if (операцияУспешна) {
resolve(результат); // промис переходит в fulfilled
} else {
reject(ошибка); // промис переходит в rejected
}
});
Здесь мы создаём переменную как новый объект Promise. А resolve и reject — это функции, которые вы не создаёте, а получаете как параметры от new Promise.
new Promise(...) означает создание объекта-обещания.
(resolve, reject) означают функции, которые будут завершать это обещание.
resolve(данные) означает «всё хорошо, вот результат», к примеру, resolve(user).
reject(ошибка) означает «всё плохо, вот причина», к примеру, reject("Сеть недоступна").
Пример:
const getUser = new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve({ id: 1, name: "Алекс" });
} else {
reject(new Error("Не удалось загрузить пользователя"));
}
}, 1000);
});
Здесь создаётся переменная getUser, выполняется операция, и при успехе передаётся результат в параметрах resolve, иначе - reject и ошибка как новый объект с текстом.
Использование промиса выполняется тремя методами - .then, .catch, .finally. Шаблонно можно сделать так:
имяПеременной
.then((результат) => {
// выполняется, если промис fulfilled
// результат — то, что передали в resolve(...)
return новыйРезультат; // можно возвращать промис или значение
})
.catch((ошибка) => {
// выполняется, если промис rejected
// ошибка — то, что передали в reject(...)
console.error("Ошибка:", ошибка);
})
.finally(() => {
// выполняется ВСЕГДА, независимо от результата
// полезно для очистки, логирования, скрытия лоадера
console.log("Загрузка завершена");
});
.then(result => { ... })здесь вызывается при успехе (resolve), может вернуть новый промис или значение. Не обязателен, но без него смысла в промисе нет..catch(error => { ... })вызывается при ошибке (reject), ловит ошибки изresolve,rejectили предыдущих.then, и рекомендуется, чтобы ошибки не потерялись. Без него ошибки «проглатываются», и вы не узнаете, что что-то пошло не так. Можно писать только.then, но если произойдёт ошибка, она не будет обработана, и в консоли появитсяunhandled promise rejection..finally(() => { ... })выполняется всегда, после .then или .catch, не получает аргументов, и является опциональным.
.then и .catch возвращают новый промис, поэтому можно строить цепочки. Каждый .then может возвращать промис — тогда следующий .then дождётся его.
Пример:
getUser
.then((user) => {
console.log("Пользователь:", user);
return user.id;
})
.then((id) => {
console.log("ID:", id);
})
.catch((error) => {
console.error("Ошибка:", error.message);
})
.finally(() => {
console.log("Готово!");
});
Здесь мы указывает что если user то печатаем одно, если id, то другое. А в любом случае выводим Готово!.
Как мы ранее усвоили, .then и .catch — это микрозадачи, которые выполняются после синхронного кода, но до setTimeout.
Чем промис отличается от колбэка?
| Колбэк (Callback) | Промис (Promise) |
|---|---|
| Функция, которая вызывается после операции. | Объект, который представляет будущий результат и сам управляет своим состоянием. |
| Мы даём номер телефона (колбэк) курьеру и говорим: «Позвони, как довезёшь». И если курьер не позвонит – пицца зависнет. | Нам дают чек на заказ (промис), у которого есть статусы. И мы точно знаем, что пицца не потерялась, чек (промис) – гарантия доставки. Мы можем заниматься своими делами. Пицца доставлена – можем кушать. Сгорела (rejected) – мне вернут деньги. |
Промисы предоставляют удобные статические методы для работы с одним или несколькими промисами. Часто их могут называть Promise API:
Promise.resolve(value)- cоздаёт уже выполненный (fulfilled) промис с переданным значением. Используется для оборачивания значения в промис, когда нужно вернуть промис, но результат уже известен, и чтобы унифицировать интерфейс: всегда возвращать промис, даже если данные синхронные. Если передать вPromise.resolve()другой промис — он просто вернёт его без изменения (но гарантирует, что это будет промис).Promise.reject(reason)- создаёт уже отклонённый (rejected) промис с указанной ошибкой. Можно использовать для создания ошибок в цепочках, в условных ветвлениях, когда нужно сразу «упасть».Promise.all(iterable)- ждёт все промисы, завершается только тогда, когда все успешно выполнились. Возвращает массив результатов. Если хотя бы один промис отклонился, весьPromise.allпереходит в состояние rejected. Это «жёсткий» режим. Использовать, когда все данные нужны обязательно (например, профиль+настройки+подписки).Promise.allSettled(iterable)ждёт завершения всех промисов, независимо от успеха или ошибки. Возвращает массив объектов с результатами. Отличие отallв том, чтоallSettledникогда не отклоняется, всегда ждёт всех. Полезно, когда важны все ответы, даже ошибочные. Пример - массовая отправка уведомлений, если нужно узнать, какие дошли, а какие нет.Promise.any(iterable)- возвращает результат первого успешного промиса. Остальные игнорируются. Падает только если все промисы отклонены (AggregateError — ошибка, содержащая массив всех причин). Использовать для реализации резервирования (failover): попробовать несколько источников, взять первый рабочий.Promise.race(iterable)- возвращает результат первого завершившегося промиса, независимо от успеха или ошибки. Очень чувствителен к скорости: если первый промис — ошибка, вся цепочка падает. Опасен: может «поймать» ошибку раньше, чем успеют выполниться успешные промисы. Часто используется с таймаутами для защиты от зависаний.
Давайте пробежимся ещё раз по промису.
Как создать промис?
const myPromise = new Promise((resolve, reject) => {
// Симуляция асинхронной операции (например, запрос к серверу, чтение файла, таймер)
const isSuccess = /* Логика проверки успеха или ошибки */;
if (isSuccess) {
resolve(/* Результат успешной операции */);
} else {
reject(/* Причина ошибки */);
}
});
Как использовать промис?
myPromise
.then((result) => {
// Обработка успешного результата
console.log("Успех:", result);
return /* Новый результат для следующего шага */;
})
.catch((error) => {
// Обработка ошибки
console.error("Ошибка:", error);
})
.finally(() => {
// Выполняется всегда, независимо от результата
console.log("Операция завершена.");
});
Разбирая пример с пиццей, можно сделать это кодом в два этапа:
- Создание промиса:
const pizzaPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const isSuccess = Math.random() > 0.5; // 50% шанс успеха
if (isSuccess) {
resolve("Пицца доставлена!"); // Успех
} else {
reject("Пицца сгорела в печи!"); // Ошибка
}
}, 2000); // Через 2 секунды
});
- Использование промиса:
pizzaPromise
.then((result) => {
console.log(result); // "Пицца доставлена!"
})
.catch((error) => {
console.error(error); // "Пицца сгорела в печи!"
});
- new Promise создаёт объект с двумя функциями:
- resolve() – вызывается при успехе;
- reject() – вызывается при ошибке.
- Через 2 секунды «кухня» (setTimeout) сообщает результат.
.then()ловит успех,.catch()– ошибку.
Хороший пример использования промисов - fetch API, современный встроенный в браузер (и Node.js с определённых версий) способ делать HTTP-запросы к серверам. Он возвращает промис. Сама по себе функция fetch() используется для того, чтобы получить данные с сайта, отправить форму, загрузить форму или взаимодействовать с API. Это замена старому способу — XMLHttpRequest.
Базовый синтаксис:
fetch(url, options)
.then(response => {
// Обработка ответа
})
.catch(error => {
// Обработка ошибок сети
});
url — адрес, куда отправляется запрос.
options — необязательный объект: метод (GET, POST и т.д.), заголовки, тело запроса и др.
fetch(...) — отправляет запрос по указанному адресу. Сервер отвечает (например, списком пользователей).
then(response => - превращает ответ в понятные данные (обычно JSON, к примеру, .then(response => response.json())).
Смысл в том, что сразу данные мы не получаем - нужно дождаться, пока промис выполнится. Когда fetch получает ответ от сервера, он передаёт его в .then() как объект response. У этого объекта есть полезные свойства и методы:
response.ok- true, если статус 200–299 (успех), иначе false;response.status- код ответа;response.statusText- текст статуса;response.headers- заголовки ответа;response.url- URL, по которому был получен ответ.response.json()- парсит ответ как JSON;response.text()- читает ответ как обычный текст;response.blob()- для файлов (картинок, PDF и тд);response.formData()- для форм.
fetch НЕ выбрасывает ошибку при HTTP-ошибках (404, 500 и т.п.). Он считает, что запрос успешно дошёл до сервера, а значит — технически всё ок. Поэтому всегда проверяйте response.ok, если важен успех операции:
fetch('/api/user')
.then(response => {
if (!response.ok) {
throw new Error(`Ошибка ${response.status}`);
}
return response.json();
})
.then(user => console.log(user));
По умолчанию fetch делает GET-запрос (получает данные). Чтобы отправить данные — нужно указать параметры:
fetch('/api/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: 'Мой пост',
content: 'Привет, мир!'
})
})
.then(response => response.json())
.then(newPost => {
console.log('Создано:', newPost);
});
fetch() используется повсюду - это получение данных в React/Vue/Angular, отправка форм без перезагрузки страницы, работа с внешними API (погода, карты, платежи), чаты, уведомления, автоподгрузка контента. В старых браузерах вроде IE он не поддерживается, по умолчанию куки не отправляет, не имеет встроенной защиты от таймаутов и прогресс загрузки нельзя легко отследить.
Паттерны использования промисов:
- Цепочка (chaining) – каждый .then возвращает новый промис:
fetch("/api/user") // Запрашиваем пользователя
.then((response) => response.json()) // Преобразуем в JSON
.then((user) => fetch(`/api/posts?userId=${user.id}`)) // Запрашиваем посты
.then((response) => response.json()) // Ещё раз JSON
.then((posts) => console.log("Посты пользователя:", posts))
.catch((err) => console.error("Ошибка:", err)); // Ловим все ошибки в цепочке
- Параллельное выполнение – запускаются несколько промисов одновременно и ждём все результаты. Если все промисы успешны -
.then()получит массив результатов. Если хотя бы один упадёт – сработает.catch.
Promise.all([
fetch("/api/users"),
fetch("/api/posts"),
fetch("/api/comments")
])
.then(([users, posts, comments]) => {
console.log("Все данные загружены:", { users, posts, comments });
})
.catch((err) => {
console.error("Один из запросов провалился:", err);
});
- Ранний выход (
Promise.resolve/reject):
let cachedData = null;
function getData() {
if (cachedData) {
return Promise.resolve(cachedData); // Немедленный успех
}
return fetch("/api/data")
.then((data) => {
cachedData = data; // Кешируем
return data;
});
}
getData().then((data) => console.log(data));
Промисы используют микрозадачи (microtask queue) – они выполняются сразу после текущего синхронного кода, но перед макрозадачами (setTimeout, UI-рендеринг).
console.log("Старт");
setTimeout(() => console.log("setTimeout"), 0);
Promise.resolve()
.then(() => console.log("Промис 1"))
.then(() => console.log("Промис 2"));
console.log("Конец");
Промис – контейнер для будущего результата.
Promise.all запускает операции параллельно.
Любая функция с async автоматически возвращает промис. Внутри неё можно использовать await.
async function makeCoffee() {
return "Эспрессо"; // Автоматически оборачивается в промис!
}
makeCoffee().then(console.log); // Выведет "Эспрессо"
★ Await – это команда повару «сначала дождись, пока сварится кофе, потом берись за бутерброд!», которая приостанавливает выполнение функции, пока промис не выполнися. При этом основной поток не блокируется.
async function makeBreakfast() {
const coffee = await makeCoffee(); // Ждём кофе
const sandwich = await makeSandwich(); // Затем бутерброд
return `${coffee} + ${sandwich}`;
}
makeBreakfast().then(console.log); // "Эспрессо + Брускетта"
Async/Await – «синтаксический сахар», синхронный стиль, который позволяет писать код, будто он синхронный. Это комбинация async и await:
- async функция всегда возвращает промис;
- await приостанавливает выполнение функции, пока промис не разрешится.
async function loadUserAndPosts() {
try {
const user = await fetch('/api/user'); // Ждём пользователя
const posts = await fetch(`/api/posts?userId=${user.id}`); // Ждём посты
return { user, posts }; // Возвращаем результат
} catch (err) {
console.error('Ошибка загрузки:', err);
throw err; // "Пробрасываем" ошибку дальше
}
}
Важные ньюансы:
awaitработает только в async-функциях;async-функциявсегда возвращает промис;asyncговорит, что функция работает с промисами;await– «стоп-сигнал» для кода, который говорит «Жди здесь, пока промис не выполнится».
★ AJAX (Asynchronous JavaScript and XML) – технология для загрузки данных без перезагрузки страницы. Браузер отправляет HTTP-запрос в фоне, сервер возвращает данные (в JSON/XML), JS динамически обновляет страницу.
Классический AJAX-алгоритм:
- создать запрос (Fetch API или XMLHttpRequest);
- отправить запрос с параметрами (метод, заголовки, тело);
- обработать ответ (JSON, текст, бинарные данные);
- обновить UI или обработать ошибку.
async function fetchData(url, method = 'GET', body = null) {
const options = { method };
if (body) options.body = JSON.stringify(body);
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
return response.json(); // Автоматический парсинг JSON
}
Event Loop – цикл событий, состоящий из фаз:
- выполнить синхронный код до конца;
- обработать микрозадачи (промисы,
queueMicrotask); - обработать макрозадачи (
setTimeout,setInterval, UI-рендеринг).
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');